ML Documentation

株価時系列予測におけるTransformers vs. LSTM

最初のブログ投稿で始めた株式時系列問題についての議論を続ける必要があると思いました。その最初の投稿では、LSTMとCNN-LSTMモデルアーキテクチャを使用して、過去の価格のみを入力として将来の株価を予測し、かなり良い結果を得ました。平均絶対パーセンテージ誤差は3パーセント以下で、状況は良好でした。その投稿で、この数値を改善する方法について議論すると述べました。当初は、異なるハイパーパラメータを持つLSTMネットワークを使用したアンサンブルLSTMモデルを作成し、それらの予測を平均して(うまくいけば)より良い結果を得ることを意図していました。しかし、私は異なる、そしてより興味深い探求ラインを追求することに少し道草を食ったことを認めなければなりません。その新しいアプローチを一言で言うと、Transformersです。

Transformerモデルアーキテクチャとは何か?

問題の詳細に入る前に、少し説明があると良いと思いました。ただし、Transformersは私が足を踏み入れたばかりの高次元機械学習概念であり、したがって私は主題専門家には程遠いことを注記しておく必要があります。しかし、教えること(または試すこと)は学習の不可欠な部分なので、進歩の精神で、始めます。

非常に高いレベルでは、Transformerは「自己注意メカニズム」を活用してトレーニング時間とパラメータ数を削減しながら高い予測性を維持するフィードフォワードニューラルネットワークアーキテクチャです。これを私がどのように概念化しているかというと:Transformersは、再帰のすべてが物事を遅くし、パラメータを追加することなく、RNNの仕事をすることができます。これは次のように機能します:Transformerのマルチヘッド注意メカニズム(TensorFlowのtf.keras.layers.MultiHeadAttentionで実装)により、モデルは特定の他のデータポイントに関連するすべてのデータポイントを追跡できます。私たちの問題のコンテキストに置くと、明日の株価を予測しようとしている場合、今日の価格は明らかに関連していますが、他の価格も同様に関連しています。例えば、今週の価格行動は3か月前に起こったことと非常に似ている可能性があり、したがって、私たちのモデルがその価格行動を「記憶」できることが有益です。この「記憶」の必要性は、最初にリカレントニューラルネットワークとLSTM(長期短期記憶)モデルを使用することを選択した理由の一つでした。以前のデータポイントをモデルに逆向きに供給することで、RNNは以前の値を重み付けでき、この記憶効果を得ることができます。Transformersが異なる点は、独特の自己注意メカニズムにより、すべての再帰なしに同じ「記憶」効果を持つことができることです:データは一度だけ供給され、自己注意が各ステップで関連するデータポイントを追跡します(例えば、3か月前の現在見ているものと似ていた価格行動に重みを付けます)。これは、データを一度だけ供給するためRNNよりもはるかに高速にトレーニングでき、はるかに少ないパラメータを必要とすることを意味し、ウィンウィンです。Transformersのより一貫した専門的な説明については、この投稿の最後に元の論文へのリンクを貼ります。しかし今のところ、私たちの問題に移りましょう。

株式時系列問題

この調査が答えようとしている質問は、「過去の株価以外何も使わずに、次の5日間の株価を予測できるか?」と簡単に定式化できます。上で示唆したように、我々はすでにこの質問に暫定的なイエスで部分的に答えています。しかし、我々が構築したモデルは、驚くほど巧妙である一方で、完璧でも特に有用でもありません:平均で3.5%オフであることは、取引に確信を持つには十分ではありません。これの一部は、問題が単純に難しく、データがノイズが多く、ほぼランダムであるためです。株式市場をランダムウォークと呼ぶのには理由があるのです。しかし、それでも我々は落胆せず、より良いモデルを追求して以下のステップを遂行していきます:

  1. Transformersのパフォーマンスを評価するための信頼できるベースラインLSTMモデルを確立する
  2. Transformerモデルを構築する
  3. モデルをトレーニングし評価する

パート1:LSTM

ベースラインモデルを構築しましょう。以前の実験から、3つの層、200ニューロンのLSTM層、および50と1ニューロンの2つの密集ノードを持つことが、この問題に非常によく機能することがわかりました。これを実装するコードは次のとおりです:

from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM

def build_lstm(etl: ETL, epochs=25, batch_size=32) -> tuple[tf.keras.Model, tf.keras.History]:
    """
    ベースラインLSTMモデルを構築、コンパイル、フィットします。
    """
    n_timesteps, n_features, n_outputs = 5, 1, 5 
    callbacks = [tf.keras.callbacks.EarlyStopping(patience=10,
                                                  restore_best_weights=True)]
    model = Sequential()
    model.add(LSTM(200, activation='relu', 
                   input_shape=(n_timesteps, n_features)))
    model.add(Dense(50, activation='relu'))
    model.add(Dense(n_outputs))
    print('ベースラインモデルをコンパイル中...')
    model.compile(optimizer='adam', loss='mse', metrics=['mae', 'mape'])
    print('モデルをフィット中...')
    history = model.fit(etl.X_train, etl.y_train,
                        batch_size=batch_size, 
                        epochs=epochs,
                        validation_data=(etl.X_test, etl.y_test),
                        verbose=1, 
                        callbacks=callbacks)
    return model, history

そして、これがコンパイル後のこのモデルに関連するモデル要約です。

これが我々のTransformerを評価するために使用するベースラインです。評価セクションでその結果について議論しますが、今のところTransformerアーキテクチャについて話しましょう。

パート2:Transformer

我々のTransformerアーキテクチャでは、Kerasドキュメント(投稿の最後にリンクします)で推奨されている構造を使用しますが、このセットアップは分類用に構築されているため、小さな変更を行います;最終出力層の活性化関数をsoftmaxからreluに変更し、損失関数を平均二乗誤差に変更します。それ以外では、実験を通じてこの問題に最も適していることがわかったハイパーパラメータを設定しました。以下が得られるものです:

def transformer_encoder(inputs, head_size, num_heads, ff_dim,
                        dropout=0, attention_axes=None):
    """
    単一のTransformerブロックを作成します。
    """
    x = layers.LayerNormalization(epsilon=1e-6)(inputs)
    x = layers.MultiHeadAttention(
        key_dim=head_size, num_heads=num_heads, dropout=dropout,
        attention_axes=attention_axes
        )(x, x)
    x = layers.Dropout(dropout)(x)
    res = x + inputs

    # Feed Forward Part
    x = layers.LayerNormalization(epsilon=1e-6)(res)
    x = layers.Conv1D(filters=ff_dim, kernel_size=1, activation="relu")(x)
    x = layers.Dropout(dropout)(x)
    x = layers.Conv1D(filters=inputs.shape[-1], kernel_size=1)(x)
    return x + res

def build_transformer(head_size, 
                      num_heads,
                      ff_dim,
                      num_trans_blocks,
                      mlp_units, dropout=0, mlp_dropout=0) -> tf.keras.Model:
    """
    多くのTransformerブロックを構築して最終モデルを作成します。
    """
    n_timesteps, n_features, n_outputs = 5, 1, 5 
    inputs = tf.keras.Input(shape=(n_timesteps, n_features))
    x = inputs 
    for _ in range(num_trans_blocks):
        x = transformer_encoder(x, head_size, num_heads, ff_dim, dropout)

    x = layers.GlobalAveragePooling1D(data_format="channels_first")(x)
    for dim in mlp_units:
        x = layers.Dense(dim, activation="relu")(x)
        x = layers.Dropout(mlp_dropout)(x)

    outputs = layers.Dense(n_outputs, activation='relu')(x)
    return tf.keras.Model(inputs, outputs)

transformer = build_transformer(head_size=128, num_heads=4, ff_dim=2, 
                                num_trans_blocks=4, mlp_units=[256], 
                                mlp_dropout=0.10, dropout=0.10, 
                                attention_axes=1)

transformer.compile(
    loss="mse",
    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3),
    metrics=["mae", 'mape'],
)

callbacks = [tf.keras.callbacks.EarlyStopping(patience=10, 
                                              restore_best_weights=True)]

t_hist = transformer.fit(data.X_train, data.y_train, batch_size=32,
                         epochs=25, validation_data=(data.X_test, data.y_test),
                         verbose=1, callbacks=callbacks)

このコードは次のようなモデルを構築します(num_trans_blocks回繰り返されます)。

これは1つのTransformerブロックで、我々のモデルはこれらを4つ積み重ねたものになります。
最終的に、我々のTransformerブロックは合計17,205パラメータを持ち、LSTMの約10分の1です。さあ、トレーニングしましょう。

パート3:トレーニングと評価

両方のネットワークをAdamオプティマイザーで25エポックトレーニングします。まず、テストデータでのベースラインモデルのパフォーマンスについて議論しましょう。最初に注記したいのは、LSTMが非常に一貫していることです。つまり、10回連続でトレーニングしても、すべての10ケースでテストセットでほぼ同じパフォーマンスの予測を提供します。また、予想よりも早くトレーニングでき、全体で143秒です。推論時間も良好で、わずか22秒です。これは、少なくともこの比較では、LSTMが前述のように171,000+パラメータという重量級であることを考慮すると、より印象的です。

LSTMの予測の可視化。

予測能力の面では、LSTMが巧妙であることがわかります(それほど驚くべきことではありません、理由があってベースラインです)。テストセットで2.44%のMAPEを記録しました。全体的に、LSTMは株式時系列データの予測に巧妙で、一貫性があり、簡単にトレーニングできるモデルです。その唯一の制限は大きいことで、したがって簡単にスケールできないことです。しかし、株価に関しては、極端に深いモデルを構築するには十分なデータがありません。そうすると実際にパフォーマンスを失い始めます。したがって、重いパラメータ数がLSTMを非常に傷つけるとは思いません。

これがTransformerのグラフです。これはMAPEが2.8%のモデルから取得されました。黄金モデルの可視化を取得できませんでした。

次に、Transformerについて議論しましょう。最初に注記したいのは、TransformerがLSTMよりも不安定だということです。どういう意味でしょうか?同じモデル(つまり:同じハイパーパラメータ)を何度も何度もトレーニングしました。結果は?MAPE 2.37%で、私が構築した株価予測最高のモデルをトレーニングしました。MAPE 2.41%の別のものも構築しました。これらの両方が、ベースラインよりも良いことに注目してください。ベースラインは一貫して2.45%-2.5%周辺でした。しかし悲しいことに、Transformerはその2つの黄金トレーニング実行と同じハイパーパラメータでも、そのパフォーマンスを一貫して再現することができませんでした。さらに、Transformerがわずかに悪いだけではありませんでした。MAPEが3%を超えた時もありました。これは、毎月または四半期ごとにモデルを構築して再トレーニングしようとしている場合に問題です。強力なTransformerモデルを持ち、それを再トレーニングして、ガラクタの山を残すかもしれません。その点で、Transformerは理想的ではありませんでした。

では、Transformerが本当に輝いたのはどこでしょうか?パラメータ数です。LSTMのパラメータ数の少し10分の1以上の重さです。これは、TransformerがLSTMよりも高速にトレーニングできることを意味し、LSTMの143秒に対して138秒しかかかりませんでした。しかし驚くことに、推論はLSTMの方が高速でした:Transformerは全体で25秒かかりました。

最後に、実際の値に対する予測の相対分散に関して、言い換えれば、我々の予測にどれだけの不確実性が組み込まれているかに関して、LSTMがTransformerを僅差で上回りました:2.4%対2.6%でした。

結論

まず第一に、Transformerのパフォーマンスは本当に印象的だったことを注記すべきです。わずか約17,000パラメータで、170,000を超えるLSTMに追いつくことができました。それは並大抵のことではありません。しかし、再トレーニングに直面して予測不可能で不安定でした。これがTransformersの一般的な特徴なのか、私の実装と使用方法の問題なのかはわかりませんが、間違いなく非常に顕著でした。大規模言語モデルをトレーニングしている場合、これは問題ではないかもしれません;英語は数週間ごとに新しいモデルを出さなければならないほど急速に変化しません。しかし、株価を予測するために、新しいデータが流入するにつれて再トレーニングする能力、以前は非常に巧妙だったモデルが今や単に平凡になることを心配する必要がないこと、それは非常に重要になります。その理由と、パラメータの節約がトレーニング時間をそれほど速くしなかった事実のために、私の考えでは、まだLSTMを選ぶと思います。さらに、これらのモデルを数十億のパラメータにスケールする必要はありません。これはTransformersが本当に輝く体制です。10億パラメータ対100億は、17,000対170,000とは非常に異なる選択です。

Transformersを初めて扱うことを本当に楽しんだ一方で、この特定の問題にはそれほど適していないと思います。しかし、実験中に思いついたアイデアがあり、それを後続の投稿で探求しようと思います:TransformerとLSTMアーキテクチャを組み合わせることで、両方の世界の最高を得ることができるでしょうか?より多くの再現性、より低いパラメータ数、より良い予測?それについてはお楽しみに。読んでくれてありがとう!

リンク:

Transformersに関する元の論文: https://proceedings.neurips.cc/paper_files/paper/2017/file/3f5ee243547dee91fbd053c1c4a845aa-Paper.pdf

ソースコードのためのColabノートブック: https://github.com/maym5/lstm_vs_transformer/blob/main/lstm_vs__transformer.ipynb

Keras Transformerドキュメント:
https://keras.io/examples/timeseries/timeseries_transformer_classification/